/*
 * mdl.c -- mdl model loader
 *
 * Copyright (c) 2005-2007 David HENRY
 *
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without
 * restriction, including without limitation the rights to use,
 * copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT.
 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
 * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
 * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 *
 */

#include "mdl.h"

/* Table of precalculated normals */
vec3_t anorms_table[162] = {
#include "anorms.h"
};

/* Palette */
unsigned char colormap[256][3] = {
#include "colormap.h"
};

/**
 * Make a texture given a skin index 'n'.
 */
GLuint
gsubMakeTextureFromSkin(int n, const struct _mdl_model *mdl) {
    int i;
    GLuint id;

    GLubyte *pixels = (GLubyte *)
            malloc(mdl->header.skinwidth * mdl->header.skinheight * 3);

    /* Convert indexed 8 bits texture to RGB 24 bits */
    for (i = 0; i < mdl->header.skinwidth * mdl->header.skinheight; ++i) {
        pixels[(i * 3) + 0] = colormap[mdl->skins[n].data[i]][0];
        pixels[(i * 3) + 1] = colormap[mdl->skins[n].data[i]][1];
        pixels[(i * 3) + 2] = colormap[mdl->skins[n].data[i]][2];
    }

    /* Generate OpenGL texture */
    glGenTextures(1, &id);
    glBindTexture(GL_TEXTURE_2D, id);

    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);

    gluBuild2DMipmaps(GL_TEXTURE_2D, GL_RGB, mdl->header.skinwidth,
            mdl->header.skinheight, GL_RGB, GL_UNSIGNED_BYTE,
            pixels);

    /* OpenGL has its own copy of image data */
    free(pixels);
    return id;
}

/**
 * Load an MDL model from file.
 *
 * Note: MDL format stores model's data in little-endian ordering.  On
 * big-endian machines, you'll have to perform proper conversions.
 */
int
gsubReadMDLModel(const char *filename, struct _mdl_model *mdl) {
    FILE *fp;
    int i;

    fp = fopen(filename, "rb");
    if (!fp) {
        MGPI_PostMessagef("readMDLModel: couldn't open \"%s\"", filename);
        return 0;
    }

    /* Read header */
    fread(&mdl->header, 1, sizeof (struct _mdl_header), fp);

    if ((mdl->header.ident != 1330660425) ||
            (mdl->header.version != 6)) {
        /* Error! */
        MGPI_PostMessagef("readMDLModel: bad version or identifier");
        fclose(fp);
        return 0;
    }

    /* Memory allocations */
    mdl->skins = (struct _mdl_skin *)
            malloc(sizeof (struct _mdl_skin) * mdl->header.num_skins);
    mdl->texcoords = (struct _mdl_texcoord *)
            malloc(sizeof (struct _mdl_texcoord) * mdl->header.num_verts);
    mdl->triangles = (struct _mdl_triangle *)
            malloc(sizeof (struct _mdl_triangle) * mdl->header.num_tris);
    mdl->frames = (struct _mdl_frame *)
            malloc(sizeof (struct _mdl_frame) * mdl->header.num_frames);
    mdl->tex_id = (GLuint *)
            malloc(sizeof (GLuint) * mdl->header.num_skins);

    mdl->iskin = 0;

    /* Read texture data */
    for (i = 0; i < mdl->header.num_skins; ++i) {
        mdl->skins[i].data = (GLubyte *) malloc(sizeof (GLubyte)
                * mdl->header.skinwidth * mdl->header.skinheight);

        fread(&mdl->skins[i].group, sizeof (int), 1, fp);
        fread(mdl->skins[i].data, sizeof (GLubyte),
                mdl->header.skinwidth * mdl->header.skinheight, fp);

        mdl->tex_id[i] = gsubMakeTextureFromSkin(i, mdl);

        free(mdl->skins[i].data);
        mdl->skins[i].data = NULL;
    }

    fread(mdl->texcoords, sizeof (struct _mdl_texcoord),
            mdl->header.num_verts, fp);
    fread(mdl->triangles, sizeof (struct _mdl_triangle),
            mdl->header.num_tris, fp);

    /* Read frames */
    for (i = 0; i < mdl->header.num_frames; ++i) {
        /* Memory allocation for vertices of this frame */
        mdl->frames[i].frame.verts = (struct _mdl_vertex *)
                malloc(sizeof (struct _mdl_vertex) * mdl->header.num_verts);

        /* Read frame data */
        fread(&mdl->frames[i].type, sizeof (long), 1, fp);
        fread(&mdl->frames[i].frame.bboxmin,
                sizeof (struct _mdl_vertex), 1, fp);
        fread(&mdl->frames[i].frame.bboxmax,
                sizeof (struct _mdl_vertex), 1, fp);
        fread(mdl->frames[i].frame.name, sizeof (char), 16, fp);
        fread(mdl->frames[i].frame.verts, sizeof (struct _mdl_vertex),
                mdl->header.num_verts, fp);
    }

    fclose(fp);
    return 1;
}

/**
 * Free resources allocated for the model.
 */
void
gsubFreeMDLModel(struct _mdl_model *mdl) {
    int i;

    if (mdl->skins) {
        free(mdl->skins);
        mdl->skins = NULL;
    }

    if (mdl->texcoords) {
        free(mdl->texcoords);
        mdl->texcoords = NULL;
    }

    if (mdl->triangles) {
        free(mdl->triangles);
        mdl->triangles = NULL;
    }

    if (mdl->tex_id) {
        /* Delete OpenGL textures */
        glDeleteTextures(mdl->header.num_skins, mdl->tex_id);

        free(mdl->tex_id);
        mdl->tex_id = NULL;
    }

    if (mdl->frames) {
        for (i = 0; i < mdl->header.num_frames; ++i) {
            free(mdl->frames[i].frame.verts);
            mdl->frames[i].frame.verts = NULL;
        }

        free(mdl->frames);
        mdl->frames = NULL;
    }
}

/**
 * Render the model at frame n.
 */
void
gsubRenderMDLFrame(int n, const struct _mdl_model *mdl) {
    int i, j;
    GLfloat s, t;
    vec3_t v;
    struct _mdl_vertex *pvert;

    /* Check if n is in a valid range */
    if ((n < 0) || (n > mdl->header.num_frames - 1))
        return;

    /* Enable model's texture */
    glBindTexture(GL_TEXTURE_2D, mdl->tex_id[mdl->iskin]);

    /* Draw the model */
    glBegin(GL_TRIANGLES);
    {
        /* Draw each triangle */
        for (i = 0; i < mdl->header.num_tris; ++i) {
            /* Draw each vertex */
            for (j = 0; j < 3; ++j) {
                pvert = &mdl->frames[n].frame.verts[mdl->triangles[i].vertex[j]];

                /* Compute texture coordinates */
                s = (GLfloat) mdl->texcoords[mdl->triangles[i].vertex[j]].s;
                t = (GLfloat) mdl->texcoords[mdl->triangles[i].vertex[j]].t;

                if (!mdl->triangles[i].facesfront &&
                        mdl->texcoords[mdl->triangles[i].vertex[j]].onseam) {
                    s += mdl->header.skinwidth * 0.5f; /* Backface */
                }

                /* Scale s and t to range from 0.0 to 1.0 */
                s = (s + 0.5) / mdl->header.skinwidth;
                t = (t + 0.5) / mdl->header.skinheight;

                /* Pass texture coordinates to OpenGL */
                glTexCoord2f(s, t);

                /* Normal vector */
                glNormal3fv(anorms_table[pvert->normalIndex]);

                /* Calculate real vertex position */
                v[0] = (mdl->header.scale[0] * pvert->v[0]) + mdl->header.translate[0];
                v[1] = (mdl->header.scale[1] * pvert->v[1]) + mdl->header.translate[1];
                v[2] = (mdl->header.scale[2] * pvert->v[2]) + mdl->header.translate[2];

                glVertex3fv(v);
            }
        }
    }
    glEnd();
}

/**
 * Render the model with interpolation between frame n and n+1.
 * interp is the interpolation percent. (from 0.0 to 1.0)
 */
void
gsubRenderMDLFrameItp(int n, float interp, const struct _mdl_model *mdl) {
    int i, j;
    GLfloat s, t;
    vec3_t norm, v;
    GLfloat *n_curr, *n_next;
    struct _mdl_vertex *pvert1, *pvert2;

    /* Check if n is in a valid range */
    if ((n < 0) || (n > mdl->header.num_frames))
        return;

    /* Enable model's texture */
    glBindTexture(GL_TEXTURE_2D, mdl->tex_id[mdl->iskin]);/* TODO: this out from here!!! */

    /* Draw the model */
    glBegin(GL_TRIANGLES);
    {
        /* Draw each triangle */
        for (i = 0; i < mdl->header.num_tris; ++i) {
            /* Draw each vertex */
            for (j = 0; j < 3; ++j) {
                pvert1 = &mdl->frames[n].frame.verts[mdl->triangles[i].vertex[j]];
                pvert2 = &mdl->frames[n + 1].frame.verts[mdl->triangles[i].vertex[j]];

                /* Compute texture coordinates */
                s = (GLfloat) mdl->texcoords[mdl->triangles[i].vertex[j]].s;
                t = (GLfloat) mdl->texcoords[mdl->triangles[i].vertex[j]].t;

                if (!mdl->triangles[i].facesfront &&
                        mdl->texcoords[mdl->triangles[i].vertex[j]].onseam) {
                    s += mdl->header.skinwidth * 0.5f; /* Backface */
                }

                /* Scale s and t to range from 0.0 to 1.0 */
                s = (s + 0.5) / mdl->header.skinwidth;
                t = (t + 0.5) / mdl->header.skinheight;

                /* Pass texture coordinates to OpenGL */
                glTexCoord2f(s, t);

                /* Interpolate normals */
                n_curr = anorms_table[pvert1->normalIndex];
                n_next = anorms_table[pvert2->normalIndex];

                norm[0] = n_curr[0] + interp * (n_next[0] - n_curr[0]);
                norm[1] = n_curr[1] + interp * (n_next[1] - n_curr[1]);
                norm[2] = n_curr[2] + interp * (n_next[2] - n_curr[2]);

                glNormal3fv(norm);

                /* Interpolate vertices */
                v[0] = mdl->header.scale[0] * (pvert1->v[0] + interp
                        * (pvert2->v[0] - pvert1->v[0])) + mdl->header.translate[0];
                v[1] = mdl->header.scale[1] * (pvert1->v[1] + interp
                        * (pvert2->v[1] - pvert1->v[1])) + mdl->header.translate[1];
                v[2] = mdl->header.scale[2] * (pvert1->v[2] + interp
                        * (pvert2->v[2] - pvert1->v[2])) + mdl->header.translate[2];

                glVertex3fv(v);
            }
        }
    }
    glEnd();
}

/**
 * Calculate the current frame in animation beginning at frame
 * 'start' and ending at frame 'end', given interpolation percent.
 * interp will be reseted to 0.0 if the next frame is reached.
 */
void
gsubAnimateMDL(int start, int end, int *frame, float *interp) {
    if ((*frame < start) || (*frame > end))
        *frame = start;

    if (*interp >= 1.0f) {
        /* Move to next frame */
        *interp = 0.0f;
        (*frame)++;

        if (*frame >= end)
            *frame = start;
    }
}

/*---OUT--------------------------------------------------------------------------------
void
init (const char *filename)
{
  GLfloat lightpos[] = { 5.0f, 10.0f, 0.0f, 1.0f };


  glClearColor (0.5f, 0.5f, 0.5f, 1.0f);
  glShadeModel (GL_SMOOTH);

  glEnable (GL_DEPTH_TEST);
  glEnable (GL_TEXTURE_2D);
  glEnable (GL_LIGHTING);
  glEnable (GL_LIGHT0);

  glLightfv (GL_LIGHT0, GL_POSITION, lightpos);


  if (!ReadMDLModel (filename, &mdlfile))
      exit (EXIT_FAILURE);
}

void
cleanup ()
{
  FreeModel (&mdlfile);
}

void
reshape (int w, int h)
{
  if (h == 0)
    h = 1;

  glViewport (0, 0, (GLsizei)w, (GLsizei)h);

  glMatrixMode (GL_PROJECTION);
  glLoadIdentity ();
  gluPerspective (45.0, w/(GLdouble)h, 0.1, 1000.0);

  glMatrixMode (GL_MODELVIEW);
  glLoadIdentity ();
}

void
display ()
{
  static int n = 0;
  static float interp = 0.0;
  static double curent_time = 0;
  static double last_time = 0;

  glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
  glLoadIdentity ();

  last_time = curent_time;
  curent_time = (double)glutGet (GLUT_ELAPSED_TIME) / 1000.0;

  interp += 10 * (curent_time - last_time);
  Animate (0, mdlfile.header.num_frames - 1, &n, &interp);

  glTranslatef (0.0f, 0.0f, -100.0f);
  glRotatef (-90.0f, 1.0, 0.0, 0.0);
  glRotatef (-90.0f, 0.0, 0.0, 1.0);


  RenderFrameItp (n, interp, &mdlfile);

  glutSwapBuffers ();
  glutPostRedisplay ();
}

void
keyboard (unsigned char key, int x, int y)
{
  switch (key)
    {
    case '+':
      mdlfile.iskin++;
      break;

    case '-':
      mdlfile.iskin--;
      break;

    case 27:
      exit (0);
      break;
    }
}

int
main (int argc, char *argv[])
{
  if (argc < 2)
    {
      fprintf (stderr, "usage: %s <filename.mdl>\n", argv[0]);
      return 0;
    }

  glutInit (&argc, argv);
  glutInitDisplayMode (GLUT_RGBA | GLUT_DOUBLE | GLUT_DEPTH);
  glutInitWindowSize (640, 480);
  glutCreateWindow ("MDL Model");

  atexit (cleanup);
  init (argv[1]);

  glutReshapeFunc (reshape);
  glutDisplayFunc (display);
  glutKeyboardFunc (keyboard);

  glutMainLoop ();

  return 0;
}
------------------------------------------------------------------------------*/
